<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>多对多关系图</title>
    <script src="https://d3js.org/d3.v6.min.js"></script>
  </head>
  <body>
    <svg width="800" height="600"></svg>
    <script>
      const data = {
        nodes: [
          { id: "A" },
          { id: "B" },
          { id: "C" },
          { id: "D" },
        ],
        links: [
          { source: "A", target: "B" },
          { source: "A", target: "C" },
          { source: "B", target: "C" },
          { source: "B", target: "D" },
          { source: "C", target: "D" },
        ],
      };
      
      const width = 800;
      const height = 600;
      
      const svg = d3.select("svg");
      
      const simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(d => d.id).distance(100))
        .force("charge", d3.forceManyBody().strength(-400))
        .force("center", d3.forceCenter(width / 2, height / 2));
      
      const link = svg.append("g")
        .attr("stroke", "#999")
        .attr("stroke-opacity", 0.6)
        .selectAll("line")
        .data(data.links)
        .join("line");
      
      const node = svg.append("g")
        .attr("stroke", "#fff")
        .attr("stroke-width", 1.5)
        .selectAll("circle")
        .data(data.nodes)
        .join("circle")
        .attr("r", 20)
        .attr("fill", "#69b3a2")
        .call(drag(simulation));
      
      node.append("title")
        .text(d => d.id);
      
      simulation.nodes(data.nodes)
        .on("tick", () => {
          link
            .attr("x1", d => d.source.x)
            .attr("y1", d => d.source.y)
            .attr("x2", d => d.target.x)
            .attr("y2", d => d.target.y);
      
          node
            .attr("cx", d => d.x)
            .attr("cy", d => d.y);
        });
      
      simulation.force("link")
        .links(data.links);
      
      function drag(simulation) {
        function dragstarted(event) {
          if (!event.active) simulation.alphaTarget(0.3).restart();
          event.subject.fx = event.subject.x;
          event.subject.fy = event.subject.y;
        }
        
        function dragged(event) {
          event.subject.fx = event.x;
          event.subject.fy = event.y;
        }
        
        function dragended(event) {
          if (!event.active) simulation.alphaTarget(0);
          event.subject.fx = null;
          event.subject.fy = null;
        }
        
        return d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended);
      }
    </script>
  </body>
</html>
